#ifndef __TIMING_HPP__
#define __TIMING_HPP__

#include <iostream>
#include <sstream>
#include <string>
#include <cassert>

#include "tbb/tick_count.h"

#include "Threading.hpp"


class MasterTimer {
  std::string name;
  double *global_timer;
public:
  MasterTimer() : global_timer(nullptr) {}
  void setTimer(std::string s, double *g) {name = s; global_timer = g; }
  ~MasterTimer() {
#ifdef MEASURE_OVERHEADS
    std::stringstream ss;
    ss << "All Threads Overhead " << name << " is: " << *global_timer << " seconds\n";
    Threading::dump(std::cerr, ss.str());
#endif
  }
};

// To be instantiated by each thread in RAII mode, each thread has a local
// copy of such an object and the global_timer points to a global variable by
// virtue of GlobalTimers being a global shared singleton.
class Timer {
#ifdef MEASURE_OVERHEADS
  double *global_timer;
  tbb::tick_count tick;

  static inline std::mutex &timer_mutex() {
    static std::mutex the_mutex;
    return the_mutex;
  }
public:
  Timer(double &g) : global_timer(&g), tick(tbb::tick_count::now()) {}
  ~Timer() {
    double d = (tbb::tick_count::now() - tick).seconds();
    // Could put an atomic here.
    timer_mutex().lock();
    *(global_timer) += d;
    timer_mutex().unlock();
    tick = tbb::tick_count::now();
  }
#endif
};

class GlobalTimers {
  double overheadGlobal;
  double overheadCNCDep;
  double overheadCNCGetPut;
  double overheadSWARMGetPut;  

  MasterTimer masterTimerGlobal;
  MasterTimer masterTimerCNCDep;
  MasterTimer masterTimerCNCGetPut;
  MasterTimer masterTimerSWARMGetPut;

  GlobalTimers() : 
    overheadGlobal(0.0),
    overheadCNCDep(0.0),
    overheadCNCGetPut(0.0),
    overheadSWARMGetPut(0.0),
    masterTimerGlobal(),
    masterTimerCNCDep(),
    masterTimerCNCGetPut(),
    masterTimerSWARMGetPut()
  {
    masterTimerCNCDep.setTimer("overheadCNCDep", &overheadCNCDep);
    masterTimerCNCGetPut.setTimer("overheadCNCGetPut", &overheadCNCGetPut);
    masterTimerSWARMGetPut.setTimer("overheadSWARMGetPut", &overheadSWARMGetPut);

    masterTimerGlobal.setTimer("overheadGlobal", &overheadGlobal);
  }

public:
  static GlobalTimers &Singleton() {
    static GlobalTimers s;
    static bool firstTime = true;
    if (firstTime) {std::cout << "Init global timers\n"; firstTime = false;}
    return s;
  } 

  static void init() {
    Singleton();
    static bool firstTime = true;
    if (firstTime) {std::cout << "Init global timers\n"; firstTime = false;}
  }

  double &getOverheadGlobal() {return overheadGlobal;}
  double &getOverheadCNCDep() {return overheadCNCDep;}
  double &getOverheadCNCGetPut() {return overheadCNCGetPut;}
  double &getOverheadSWARMGetPut() {return overheadSWARMGetPut;}
};
#endif // __TIMING_HPP__
